package cn.tk.service.mr.impl;

import cn.tk.cache.redis.RedisConstant;
import cn.tk.cache.redis.RedisService;
import cn.tk.engine.mr.MrReqContext;
import cn.tk.model.pojo.mr.engine.ClarifyItem;
import cn.tk.model.pojo.mr.engine.ClarifyResult;
import cn.tk.model.pojo.mr.engine.MrReq;
import cn.tk.model.pojo.mr.engine.MrSession;
import cn.tk.model.pojo.mr.flow.ContextPair;
import cn.tk.model.pojo.mr.nlu.NluResp;
import cn.tk.service.mr.SessionService;
import cn.tk.utils.EmptyUtil;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Created by denglw on 2021/8/7.<br/>
 * Desc: 会话缓存服务实现层
 */
@Service
public class SessionServiceImpl implements SessionService {

    @Autowired
    private RedisService service;

    @Override
    public boolean existSession(String userId, String sessionId) {
        String key = this.key(userId, sessionId);
        StringRedisTemplate template = service.getStringWriteTemplate(key);
        Boolean hasKey = template.hasKey(key);
        return Objects.equals(hasKey, true);
    }

    @Override
    public void delete(String userId, String sessionId) {
        String key = this.key(userId, sessionId);
        StringRedisTemplate template = service.getStringWriteTemplate(key);
        template.delete(key);
    }

    @Override
    public void saveRound(MrSession.Round round) {
        if (round == null) {
            return;
        }
        String key = this.currKey();
        StringRedisTemplate template = service.getStringWriteTemplate(key);
        ListOperations<String, String> ops = template.opsForList();
        ops.leftPush(key, JSON.toJSONString(round));
        // 设置过期时间
        template.expire(key, RedisConstant.EXPIRE, TimeUnit.HOURS);
    }

    @Override
    public MrSession.Round fetchLastRound() {
        MrSession.Round currRound = this.fetchCurrRound();
        Integer serialNo = currRound.getSerialNo();
        if (serialNo == null || serialNo == 1) {
            return null;
        }
        int lastSerialNo = serialNo - 1;
        List<MrSession.Round> rounds = this.range();
        return rounds.stream().filter(i -> Objects.equals(i.getSerialNo(), lastSerialNo)).findFirst().orElse(null);
    }

    @Override
    public MrSession.Round fetchCurrRound() {
        List<MrSession.Round> rounds = this.range();
        String currMessageId = MrReqContext.currMessageId();
        return rounds.stream().filter(i ->
                Objects.equals(i.getMessageId(), currMessageId)).findFirst().orElse(new MrSession.Round());
    }

    @Override
    public int maxSerialNo() {
        List<MrSession.Round> rounds = this.range();
        return rounds.stream().map(MrSession.Round::getSerialNo).filter(Objects::nonNull).max(Integer::compareTo).orElse(0);
    }

    @Override
    public List<MrSession.Round> fetchRounds() {
        MrReq mrReq = MrReqContext.getContext();
        return this.fetchRounds(mrReq.getUserId(), mrReq.getSessionId());
    }

    @Override
    public List<MrSession.Round> fetchRounds(String userId, String sessionId) {
        List<MrSession.Round> resultList = new ArrayList<>();
        List<MrSession.Round> repeatRounds = this.repeatRounds(userId, sessionId);
        List<String> messageIds = new ArrayList<>();
        for (MrSession.Round i : repeatRounds) {
            String messageId = i.getMessageId();
            if (messageIds.contains(messageId)) {
                continue;
            }
            resultList.add(i);
            messageIds.add(messageId);
        }
        resultList.sort(Comparator.comparing(MrSession.Round::getSerialNo));
        return resultList;
    }

    @Override
    public List<String> messageContext() {
        List<MrSession.Round> rounds = this.fetchRounds();
        return rounds.stream().map(MrSession.Round::getMsg).collect(Collectors.toList());
    }

    @Override
    public int getRepeatNum(Long nodeId) {
        List<MrSession.Round> rounds = this.fetchRounds();
        List<Long> nodeIds = new ArrayList<>();
        for (MrSession.Round round : rounds) {
            nodeIds.addAll(round.getNodeIds());
        }
        if (EmptyUtil.emptyColl(nodeIds)) {
            return 0;
        }
        long count = nodeIds.stream().filter(i -> Objects.equals(i, nodeId)).count();
        return (int) count;
    }

    @Override
    public List<ContextPair> getContextPairs() {
        List<MrSession.Round> rounds = this.fetchRounds();
        List<ContextPair> pairs = new ArrayList<>();
        for (MrSession.Round round : rounds) {
            pairs.addAll(round.getContextPairs());
        }
        return pairs;
    }

    @Override
    public List<ClarifyItem> getClarifyItems(Long entityNodeId) {
        List<MrSession.Round> rounds = this.fetchRounds();
        // 进行倒序排序
        rounds.sort(Comparator.comparing(MrSession.Round::getSerialNo).reversed());
        List<ClarifyItem> items = new ArrayList<>();
        for (MrSession.Round round : rounds) {
            ClarifyResult clarifyResult = round.getClarifyResult();
            if (clarifyResult == null || !Objects.equals(clarifyResult.getNodeId(), entityNodeId)) {
                continue;
            }
            List<ClarifyItem> clarifyItems = clarifyResult.getClarifyItems();
            if (EmptyUtil.emptyColl(clarifyItems)) {
                continue;
            }
            // 去重
            for (ClarifyItem clarifyItem : clarifyItems) {
                boolean contains = items.stream().anyMatch(i ->
                        Objects.equals(i.getEntityId(), clarifyItem.getEntityId()));
                if (contains) {
                    continue;
                }
                items.add(clarifyItem);
            }
        }
        return items;
    }

    @Override
    public Map<String, String> getCollects() {
        List<MrSession.Round> rounds = this.fetchRounds();
        Map<String, String> collects = new HashMap<>(16);
        for (MrSession.Round round : rounds) {
            collects.putAll(round.getCollects());
        }
        return collects;
    }

    @Override
    public int getAndIncrRecursionNum(String messageId, String keyRandom) {
        String recursionKey = this.currRecursionKey(messageId, keyRandom);
        StringRedisTemplate template = service.getStringWriteTemplate(recursionKey);
        String numStr = template.opsForValue().get(recursionKey);
        int num = 0;
        if (!EmptyUtil.blank(numStr)) {
            num = Integer.parseInt(numStr);
        }
        int incrNum = num + 1;
        template.opsForValue().set(recursionKey, String.valueOf(incrNum));
        template.expire(recursionKey, RedisConstant.EXPIRE, TimeUnit.HOURS);
        return num;
    }

    @Override
    public List<NluResp.InnerEntity> getNluEntities() {
        List<MrSession.Round> rounds = this.fetchRounds();
        rounds.sort(Comparator.comparing(MrSession.Round::getSerialNo).reversed());
        List<NluResp.InnerEntity> resultList = new ArrayList<>();
        for (MrSession.Round round : rounds) {
            NluResp nlu = round.getNlu();
            if (nlu == null) {
                continue;
            }
            List<NluResp.InnerEntity> innerEntities = nlu.getEntities();
            if (EmptyUtil.emptyColl(innerEntities)) {
                continue;
            }
            // 过滤掉值为空的情况
            List<NluResp.InnerEntity> filters = innerEntities.stream()
                    .filter(i -> EmptyUtil.nonBlank(i.getValue())).collect(Collectors.toList());
            // 禁止重复写入
            for (NluResp.InnerEntity filter : filters) {
                boolean anyMatch = resultList.stream().anyMatch(i ->
                        Objects.equals(i.getEntityCode(), filter.getEntityCode()) && Objects.equals(i.getValue(), filter.getValue()));
                if (anyMatch) {
                    continue;
                }
                resultList.add(filter);
            }
        }
        return resultList;
    }

    /**
     * 未去重的轮次列表
     */
    private List<MrSession.Round> repeatRounds(String userId, String sessionId) {
        String key = this.key(userId, sessionId);
        StringRedisTemplate template = service.getStringReadTemplate(key);
        ListOperations<String, String> ops = template.opsForList();
        List<String> ranges = ops.range(key, 0L, -1L);
        if (EmptyUtil.emptyColl(ranges)) {
            return new ArrayList<>();
        }
        List<MrSession.Round> rounds = new ArrayList<>();
        for (String i : ranges) {
            MrSession.Round round = JSON.parseObject(i, MrSession.Round.class);
            rounds.add(round);
        }
        return rounds;
    }

    /**
     * 获取当前请求的key.<br/>
     */
    private String currKey() {
        MrReq context = MrReqContext.getContext();
        String userId = context.getUserId();
        String sessionId = context.getSessionId();
        String suffix = userId + RedisConstant.SPLIT + sessionId;
        return RedisConstant.MR_SESSION + suffix;
    }

    /**
     * 获取当前请求的递归key.<br/>
     */
    private String currRecursionKey(String messageId, String keyRandom) {
        MrReq context = MrReqContext.getContext();
        String userId = context.getUserId();
        String sessionId = context.getSessionId();
        String split = RedisConstant.SPLIT;
        String suffix = keyRandom + split + userId + split + sessionId + split + messageId;
        return RedisConstant.MR_RECURSION_NUM + suffix;
    }

    /**
     * 获取指定用户会话的缓存键.<br/>
     *
     * @param userId    用户ID
     * @param sessionId 会话ID
     * @return 键
     */
    private String key(String userId, String sessionId) {
        if (EmptyUtil.anyBlank(userId, sessionId)) {
            return UUID.randomUUID().toString();
        }
        String suffix = userId + RedisConstant.SPLIT + sessionId;
        return RedisConstant.MR_SESSION + suffix;
    }

    /**
     * 遍历队列, 最大取100个元素.<br/>
     *
     * @return 列表
     */
    private List<MrSession.Round> range() {
        String key = this.currKey();
        StringRedisTemplate template = service.getStringWriteTemplate(key);
        ListOperations<String, String> ops = template.opsForList();
        // 左遍历，与当前messageId匹配;
        // 如果长度超过100，只获取从左至100个元素进行匹配;
        // 失败则认为是机器人消息, 不维护其会话信息
        long size = EmptyUtil.nullToZero(ops.size(key));
        long rangeEnd = Math.min(size, 100L);
        List<String> ranges = ops.range(key, 0L, rangeEnd);
        if (EmptyUtil.emptyColl(ranges)) {
            return new ArrayList<>();
        }
        return JSON.parseArray(ranges.toString(), MrSession.Round.class);
    }

}
